/**
 * @file docker.c
 * @author 李率铭
 * @brief
 * @version 0.1
 * @date 2024-03-05
 * 只针对docker
 * @copyright Copyright (c) 2024
 *
 */
#include "docker.h"

/// @brief docker请求涉及到不同的content-type，所以在新写一个
/// @param url
/// @param postParams
/// @param req 1:post 2:delete
/// @param action 1:Content-xtar 2:content-json
/// @param response
/// @return
CURLcode docker_http_req(char *url, char *postParams, int req, int action, MemoryStruct *response)
{
    CURL *curl = curl_easy_init();
    // res code
    CURLcode res;

    // nConnectTimeout：连接超时时间，单位为秒；
    // nTimeout：读写数据超时时间，单位为秒
    int nConnectTimeout = 10;
    int nTimeout = 10;
    switch (req)
    {
    case 1:
        // 创建一个空的HTTP POST请求
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
        break;
    case 2:
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    default:
        break;
    }

    // 设置请求的URL
    curl_easy_setopt(curl, CURLOPT_URL, url);

    // 设置请求体类型为x-tar
    struct curl_slist *headers = NULL;
    switch (action)
    {
    case 1: // x-tar
        headers = curl_slist_append(headers, "Content-Type: application/x-tar");
        break;
    case 2:
        headers = curl_slist_append(headers, "Content-Type: application/json");
        break;
    default:
        dzlog_error("不支持的指令类型");
        break;
    }
    FILE *imageFile = NULL;
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    if (action == 1)
    {
        imageFile = fopen(postParams, "rb");
        if (imageFile)
        {
            curl_easy_setopt(curl, CURLOPT_READDATA, imageFile);
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
        }
        else
        {
            dzlog_error("Failed to open image file: %s\n", postParams);
        }
    }
    else if (action = 2)
    {
        if (postParams != NULL)
        {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postParams);
        }
    }

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // 执行可以显示执行过程日志
    curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, DOCKER_SOCK);

    // 执行HTTP请求
    res = curl_easy_perform(curl);
    if (res != CURLE_OK)
    {
        dzlog_error("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
    }
    if (imageFile)
    {
        // 关闭文件
        fclose(imageFile);
    }
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response->rescode);
    // 清理资源
    curl_easy_cleanup(curl);
    if (headers != NULL)
    {
        curl_slist_free_all(headers); // free the list again
    }
    return res;
}

bool container_sql_insert(IOTA_CONTAINER_INFO param, char *id)
{
    bool ret = false;

    sqlite3_stmt *stmt;
    const char *sql = "INSERT INTO container_list (type, containerid , id , containername) VALUES (?, ?, ?, ?)";

    int rc = sqlite3_prepare_v2(g_sqlite3_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        dzlog_error("SQL: %s error:%s\n", sql, sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }

    // 绑定参数的值
    sqlite3_bind_text(stmt, 1, param.containerHyperv, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, param.containerId, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, id, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 4, param.containerName, -1, SQLITE_STATIC);

    // 因为要3.14版本以上的sqlite支持，注释掉
    //  char *expanded_sql = sqlite3_expanded_sql(stmt);
    //  if (expanded_sql != NULL)
    //  {
    //      dzlog_debug("生成的 SQL 语句: %s\n", expanded_sql);
    //      sqlite3_free(expanded_sql);
    //  }

    // 执行 SQL 语句
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        dzlog_error("执行 SQL 语句出错: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }

    // 重置预处理语句以便可重复使用
    sqlite3_reset(stmt);
    ret = true;

error:
    // 释放资源
    if(stmt)
    {
       sqlite3_finalize(stmt); 
    }
    return ret;
}

bool container_sql_search(char *containerid, char *id)
{
    bool ret = false;

    sqlite3_stmt *stmt;
    const char *selectQuery = "SELECT id FROM container_list WHERE containerid = ?";
    int rc = sqlite3_prepare_v2(g_sqlite3_db, selectQuery, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        dzlog_error("无法编译SQL语句: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }
    rc = sqlite3_bind_text(stmt, 1, containerid, -1, SQLITE_STATIC);
    if (rc != SQLITE_OK)
    {
        dzlog_error("无法绑定参数: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }
    // 执行查询
    if ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
    {
        const char *sql_appname = (const char *)sqlite3_column_text(stmt, 0);
        if (sql_appname != NULL)
        {
            strcpy(id, sql_appname);
        }
        ret = true;
    }
    else if (rc == SQLITE_DONE)
    {
        dzlog_debug("未找到匹配的记录\n");
        goto error;
    }
    else
    {
        dzlog_error("无法执行查询: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }

error:
    if (stmt)
    {
        // 释放资源
        sqlite3_finalize(stmt);
    }

    dzlog_info("container_sql_search end");
    return ret;
}

bool container_sql_delete(char *containerid)
{
    bool ret = false;

    sqlite3_stmt *stmt;
    const char *selectQuery = "DELETE FROM container_list WHERE containerid = ?";
    int rc = sqlite3_prepare_v2(g_sqlite3_db, selectQuery, -1, &stmt, NULL);

    if (rc != SQLITE_OK)
    {
        dzlog_error("无法编译SQL语句: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }

    rc = sqlite3_bind_text(stmt, 1, containerid, -1, SQLITE_STATIC);
    if (rc != SQLITE_OK)
    {
        dzlog_error("无法绑定参数: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }

    // 执行 SQL 语句
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        dzlog_error("删除数据失败: %s\n", sqlite3_errmsg(g_sqlite3_db));
        goto error;
    }
    else
    {
        dzlog_info("成功删除containerid %s", containerid);
        ret = true;
    }

error:
    if (stmt)
    {
        // 释放语句对象
        sqlite3_finalize(stmt);
    }

    return ret;
}

int container_install_json(char **pcJson, IOTA_CONTAINER_INFO param, char *imageName)
{
    cJSON *json = cJSON_CreateObject();
    cJSON *host = cJSON_CreateObject();
    cJSON *Cmd = cJSON_CreateArray();
    cJSON_AddItemToArray(Cmd, cJSON_CreateString("/bin/sh"));
    cJSON_AddStringToObject(json, "Image", imageName);
    // 必要参数
    cJSON_AddBoolToObject(json, "AttachStdin", true);
    cJSON_AddBoolToObject(json, "AttachStdout", true);
    cJSON_AddBoolToObject(json, "AttachStderr", true);
    cJSON_AddBoolToObject(json, "Tty", true);
    cJSON_AddBoolToObject(json, "OpenStdin", true);
    cJSON_AddBoolToObject(json, "StdinOnce", true);

    cJSON_AddItemToObject(json, "Cmd", Cmd);
    if (param.cpuCount != 0)
    {
        cJSON_AddNumberToObject(host, "CpuCount", param.cpuCount);
    }
    if (param.cpuPeriod != 0)
    {
        cJSON_AddNumberToObject(host, "CpuPeriod", param.cpuPeriod);
    }
    if (param.cpuQuota != 0)
    {
        cJSON_AddNumberToObject(host, "CpuQuota", param.cpuQuota);
    }
    if (param.memorySize)
    {
        cJSON_AddNumberToObject(host, "Memory", param.memorySize * 1024 * 1024);
    }
    if (param.devices != NULL)
    {
        cJSON *devices = cJSON_CreateArray();
        int i = 0;
        for (i = 0; i < param.devicecount; i++)
        {
            cJSON *device = cJSON_CreateObject();
            cJSON_AddStringToObject(device, "PathOnHost", param.devices[i].key);
            cJSON_AddStringToObject(device, "PathInContainer", param.devices[i].value);
            cJSON_AddItemToArray(devices, device);
        }
        cJSON_AddItemToObject(host, "Devices", devices);
    }

    if (param.volumes != NULL)
    {
        // TODO 关于设置volume先暂时不管，后面在开发
        cJSON *volumes = cJSON_CreateArray();
        int i;
        for (i = 0; i < param.volumecount; i++)
        {
            cJSON *volume = cJSON_CreateObject();
            // cJSON_AddStringToObject(volume, "PathOnHost", param.devices[i].key);
            // cJSON_AddStringToObject(volume, "PathInContainer", param.devices[i].value);
            cJSON_AddItemToArray(volumes, volume);
        }
    }
    cJSON_AddItemToObject(json, "HostConfig", host);
    // ip
    cJSON *IPAMConfig = NULL;
    cJSON *isolated_nw = NULL;
    cJSON *EndpointsConfig = NULL;
    cJSON *NetworkingConfig = NULL;

    cJSON_AddStringToObject(IPAMConfig, "IPv4Address", param.ip);
    cJSON_AddItemToObject(isolated_nw, "IPAMConfig", IPAMConfig);
    cJSON_AddItemToObject(EndpointsConfig, "isolated_nw", isolated_nw);
    cJSON_AddItemToObject(NetworkingConfig, "EndpointsConfig", EndpointsConfig);
    cJSON_AddItemToObject(json, "NetworkingConfig", NetworkingConfig);

    *pcJson = cJSON_PrintUnformatted(json);
    cJSON_Delete(json);
    return 1;
}

int container_docker_install(IOTA_CONTAINER_INFO param)
{
    // 上传镜像
    char *pcJson = NULL;
    int i = 0;
    int no = 0;
    char filenametemp[128] = {0};

    char url[100] = {0};
    sprintf(url, "%s/images/load", DOCKER_URL);

    // 文件名
    // 判断下文件名是否是以img结尾的
    // if (strlen(param.imageName) >= 4)
    // {
    //     if (strcmp(param.imageName + strlen(param.imageName) - 4, ".img") == 0)
    //     {
    //         sprintf(filename, "%s", param.imageName);
    //     }
    //     else
    //     {
    //         sprintf(filename, "%s.img", param.imageName);
    //     }
    // }
    char imageName[70] = {0};
    sprintf(filenametemp, "%s/%s", DOCKER_PATH, param.imageName);
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;
    bool error = false;
    CURLcode code;
    code = docker_http_req(url, filenametemp, 1, 1, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 200)
        {
            dzlog_error("返回出错：%s", oDataChunk.memory);
            error = true;
        }
        else
        {
            dzlog_info("upload respose = %s", oDataChunk.memory);
            // 提取返回报文中的Imagename
            cJSON *root = cJSON_Parse(oDataChunk.memory);
            if (cJSON_HasObjectItem(root, "errorDetail"))
            {
                error = true;
            }
            else
            {
                char *temp = cJSON_GetObjectItem(root, "stream")->valuestring;
                const char *start = strstr(temp, "Loaded image: ");
                if (start != NULL)
                {
                    start += strlen("Loaded image: ");
                    size_t length = strlen(start);
                    strncpy(imageName, start, length);
                    imageName[length - 1] = '\0';
                }
            }
        }
    }
    if (error)
    {
        return 542;
    }
    error = false;
    oDataChunk.size = 0;
    // 生成容器
    memset(url, 0, sizeof(url));
    sprintf(url, "%s/containers/create?name=%s", DOCKER_URL, param.containerName);
    container_install_json(&pcJson, param, imageName);
    code = docker_http_req(url, pcJson, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 201)
        {
            dzlog_error("返回出错：%s", oDataChunk.memory);
            error = true;
        }
        else
        {
            dzlog_info("install respose = %s", oDataChunk.memory);
            cJSON *body = cJSON_Parse(oDataChunk.memory);
            char *id = cJSON_GetObjectItem(body, "Id")->valuestring;
            char containerID[13] = {0};
            strncpy(containerID, id, 12);
            container_sql_insert(param, containerID);
            cJSON_Delete(body);
        }

        free(oDataChunk.memory);
    }
    if (error)
    {
        return 534;
    }

    free_s(pcJson);
    return 1;
}

int container_docker_uninstall(IOTA_CONTAINER_CTRL param)
{
    char id[60] = {0};
    if (!container_sql_search(param.containerId, &id))
    {
        dzlog_error("container_docker_uninstall not found container[%s]", param.containerName);
        return 535;
    }
    char url[60] = {0};
    sprintf(url, "%s/containers/%s", DOCKER_URL, id);
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;
    bool error = false;
    CURLcode code = docker_http_req(url, NULL, 2, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("container_docker_uninstall post error：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 204)
        {
            dzlog_error("container_docker_uninstall return error[%s]", oDataChunk.memory);
            error = true;
        }
        free(oDataChunk.memory);
    }
    if (error)
    {
        return 539;
    }
    container_sql_delete(param.containerId);
    return 1;
}

int container_docker_start(IOTA_CONTAINER_CTRL param)
{
    dzlog_info("container_docker_start start 0");
    char id[60] = {0};
    if (!container_sql_search(param.containerId, &id))
    {
        dzlog_error("not find %s docker", param.containerName);
        return 535;
    }
    char url[128] = {0};
    sprintf(url, "%s/containers/%s/start", DOCKER_URL, id);
    dzlog_info("container_docker_start start 2 url:%s", url);
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;
    bool error = false;
    CURLcode code = docker_http_req(url, NULL, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("container_docker_start post error[%s]", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 204)
        {
            dzlog_error("container_docker_start return [%s]", oDataChunk.memory);
            error = true;
        }
        free(oDataChunk.memory);
    }
    if (error)
    {
        return 535;
    }
    return 1;
}

int container_docker_stop(IOTA_CONTAINER_CTRL param)
{
    char id[60] = {0};
    if (!container_sql_search(param.containerId, &id))
    {
        dzlog_error("container_docker_stop not found container[%s]", param.containerName);
        return 536;
    }
    char url[60] = {0};
    sprintf(url, "%s/containers/%s/stop", DOCKER_URL, id);
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;
    bool error = false;
    CURLcode code = docker_http_req(url, NULL, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("container_docker_stop post error：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 204)
        {
            dzlog_error("container_docker_stop return error：%s", oDataChunk.memory);
            error = true;
        }
        free(oDataChunk.memory);
    }
    if (error)
    {
        return 536;
    }
    return 1;
}

/// @brief 容器克隆
/// @param param
/// @return
int container_docker_clone(IOTA_CONTAINER_INFO param)
{

    char id[20] = {0};
    if (!container_sql_search(param.srcContainerId, &id))
    {
        dzlog_error("container_docker_clone not found Container[%s]", param.srcContainerName);
        return 536;
    }
    char url[80] = {0};
    bool error = false;
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;

    // 生成镜像
    memset(url, 0, sizeof(url));
    sprintf(url, "%s/commit?container=%s&repo=%s", DOCKER_URL, id, param.imageName);
    if (param.imageTag != NULL)
    {
        strcat(url, "&tag=");
        strcat(url, param.imageTag);
    }
    else
    {
        strcat(url, "&tag=latest");
    }
    char imageID[50] = {0};
    CURLcode code = docker_http_req(url, NULL, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("container_docker_clone post error：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 201)
        {
            dzlog_error("container_docker_clone return error ：%s", oDataChunk.memory);
            error = true;
        }
        else
        {
            dzlog_info("commit respose = %s", oDataChunk.memory);
            cJSON *body = cJSON_Parse(oDataChunk.memory);
            char *id = cJSON_GetObjectItem(body, "Id")->valuestring;

            // 找到第一个分号的位置
            char *ptr = strchr(id, ':');
            strcpy(imageID, ptr + 1);

            cJSON_Delete(body);
        }

        free(oDataChunk.memory);
    }
    if (error)
    {
        return 540;
    }
    char *pcJson = NULL;
    error = false;
    oDataChunk.size = 0;
    // 生成容器
    memset(url, 0, sizeof(url));
    sprintf(url, "%s/containers/create?name=%s", DOCKER_URL, param.containerName);
    container_install_json(&pcJson, param, imageID);
    code = docker_http_req(url, pcJson, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 201)
        {
            dzlog_error("返回出错：%s", oDataChunk.memory);
            error = true;
        }
        else
        {
            dzlog_info("install respose = %s", oDataChunk.memory);
            cJSON *body = cJSON_Parse(oDataChunk.memory);
            char *id = cJSON_GetObjectItem(body, "Id")->valuestring;
            char containerID[13] = {0};
            strncpy(containerID, id, 12);
            container_sql_insert(param, containerID);
            cJSON_Delete(body);
        }

        free(oDataChunk.memory);
    }
    if (error)
    {
        return 540;
    }

    free_s(pcJson);
    return 1;
}

int container_docker_update(IOTA_CONTAINER_CTRL param)
{
    dzlog_info("update start");
    char id[20] = {0};
    if (!container_sql_search(param.containerId, &id))
    {
        dzlog_error("找不到容器名为%s的容器", param.containerName);
        return 536;
    }

    char url[80] = {0};
    char *setinfo = NULL;
    bool error = false;
    MemoryStruct oDataChunk;
    oDataChunk.memory = malloc(1); // 初始化内存
    oDataChunk.size = 0;
    int infolen = 0;
    char filenametemp[70] = {0};
    char imageName[70] = {0};
    int i = 0;
    int no = 0;
    char *infotmp = NULL;

    // 保存配置
    sprintf(url, "%s/containers/%s/json", DOCKER_URL, id);
    CURLcode code = docker_http_req(url, NULL, 1, 2, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
        goto Error;
    }
    if (oDataChunk.rescode == 200)
    {
        infolen = strlen(oDataChunk.memory);
    }
    infotmp = malloc(infolen + 1);
    strcpy(infotmp, oDataChunk.memory);
    dzlog_info("container info = %s", infotmp);

    // 先停止，再卸载
    int ret = container_docker_stop(param);
    if (ret != 1)
    {
        return ret;
    }
    ret = container_docker_uninstall(param);
    if (ret != 1)
    {
        return ret;
    }

    // 上传镜像
    dzlog_info("upload images");
    oDataChunk.size = 0;
    memset(url, 0, sizeof(url));
    sprintf(url, "%s/images/load", DOCKER_URL);

    sprintf(filenametemp, "%s/%s", DOCKER_PATH, param.filename);
    code = docker_http_req(url, filenametemp, 1, 1, &oDataChunk);
    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
        goto Error;
    }
    else
    {
        if (oDataChunk.rescode != 200)
        {
            dzlog_error("返回出错：%s", oDataChunk.memory);
            error = true;
            goto Error;
        }
        else
        {
            dzlog_info("upload respose = %s", oDataChunk.memory);
            // 提取返回报文中的Imagename
            cJSON *root = cJSON_Parse(oDataChunk.memory);
            if (cJSON_HasObjectItem(root, "errorDetail"))
            {
                error = true;
            }
            else
            {
                char *temp = cJSON_GetObjectItem(root, "stream")->valuestring;
                const char *start = strstr(temp, "Loaded image: ");
                if (start != NULL)
                {
                    start += strlen("Loaded image: ");
                    size_t length = strlen(start);
                    strncpy(imageName, start, length);
                    imageName[length - 1] = '\0';
                }
            }
        }
    }

    cJSON *json = cJSON_Parse(infotmp);
    cJSON_GetObjectItemCaseSensitive(json, "Image")->valuestring = imageName;
    setinfo = cJSON_PrintUnformatted(json);
    // 生成容器
    dzlog_info("container create");
    memset(url, 0, sizeof(url));
    oDataChunk.size = 0;
    sprintf(url, "%s/containers/create?name=%s", DOCKER_URL, param.containerName);

    code = docker_http_req(url, setinfo, 1, 2, &oDataChunk);

    if (code != CURLE_OK)
    {
        dzlog_error("请求出错：%s", curl_easy_strerror(code));
        error = true;
    }
    else
    {
        if (oDataChunk.rescode != 201)
        {
            dzlog_error("返回出错：%s", oDataChunk.memory);
            error = true;
            goto Error;
        }
        else
        {
            dzlog_info("install respose = %s", oDataChunk.memory);
            cJSON *body = cJSON_Parse(oDataChunk.memory);
            char *id = cJSON_GetObjectItem(body, "Id")->valuestring;
            char containerID[13] = {0};
            strncpy(containerID, id, 12);
            IOTA_CONTAINER_INFO tmp;
            tmp.containerHyperv = "docker";
            tmp.containerId = param.containerId;
            tmp.containerName = param.containerName;
            container_sql_insert(tmp, containerID);
            cJSON_Delete(body);
        }

        free_s(oDataChunk.memory);
    }
    if (error)
    {
        return 540;
    }
Error:
    if (error)
    {
        return 0;
    }
    free_s(setinfo);
    free_s(infotmp);
}